
const { ccclass, property } = cc._decorator;
interface IBorder {
    leftSide?: number;
    rightSide?: number;
    bottomSide?: number;
    topSide?: number;
}
type TDate = { size?: cc.Size };
export class ShowNode<T extends TDate> extends cc.Component {
    /**初始化操作 */
    initShow(data: T, node: LoopListCell<T>, renderLayer: cc.Node) { }
    /**被回收，应该做一些释放的操作 */
    onRecircle() { }
}
@ccclass
export default class LoopListCell<T extends TDate> extends cc.Component {
    /**持有cell的list */
    public looplist: LoopList<T> = null;
    /**显示节点 */
    public showNode: cc.Node = null;
    /**尺寸 */
    public size: cc.Size = null;
    /**数据 */
    public data: T = null;

    /**
     * 初始化
     * @param data 
     * @param size 
     */
    public init(data: T, size: cc.Size, looplist: LoopList<T>) {
        this.looplist = looplist;
        this.node.width = size.width;
        this.node.height = size.height;
        this.data = data;
        this.size = size;
    }
    /**
     * 重置大小
     * @param size 
     */
    resize(size: cc.Size) {
        let nodeSides = getNodeBorder(this.node, [true, true, true, true]);
        let changeSize = cc.size(size.width - this.size.width, size.height - this.size.height);
        this.size = size;
        this.looplist.dealChangeSize(this, nodeSides, changeSize).then(() => {
            this.node.width = size.width;
            this.node.height = size.height;
            this.looplist.updateNodes();
        });
    }

    /**
     * 
     * 更新节点显示
     * @param isShow 是否显示
     * @param prefab 需要显示的节点的预制件（也可以是节点，或者预制件url,生成方式自由改动）
     * @param cacheNode 对象池中的显示节点
     * @param renderLayer 分层节点
     */
    updateShow(isShow: boolean, prefab: cc.Prefab, cacheNode: cc.Node, renderLayer: cc.Node) {
        if (isShow) {
            //当前没有显示节点
            if (!this.showNode) {
                //如果是临时对象池还有节点则重用
                if (cacheNode) {
                    this.showNode = cacheNode;
                } else {//否则使用传入的预制件进行生成
                    this.showNode = cc.instantiate(prefab);
                }
                this.showNode.setParent(this.node);
                this.showNode.position = cc.v3();
                this.showNode.getComponent(ShowNode).initShow(this.data, this, renderLayer);
            }
        }
    }

    get hasNoNode() {
        return !this.showNode;
    }

    recircle() {
        if (this.showNode) {
            let node = this.showNode;
            this.showNode.getComponent(ShowNode).onRecircle();
            this.showNode.removeFromParent(false);
            this.showNode = null;
            return node;
        }
    }
}

/**
 * 目前列表还存在的问题
 * 1.在子节点进行动态大小变化时没有提醒loop进行节点视窗更新，
 * 导致的问题就是content内容会自动位移，应该计算变化后让content的位置进行相对偏移保证视窗内容不自动位移
 */
export class LoopList<T extends TDate> {
    public static insMap: Map<string, LoopList<any>> = new Map();
    public static ins<T>(listName: string): LoopList<T> {
        if (!this.insMap.has(listName)) {
            this.insMap.set(listName, new LoopList<T>());
        }
        return this.insMap.get(listName) as LoopList<T>;
    }
    /**分层节点（只有在showNode中挂载了RenderAlternative才能起作用） */
    public renderLayer: cc.Node = null;
    /**滚动组件 */
    public scrollView: cc.ScrollView = null;
    /**数据 */
    public datas: T[] = [];
    /**item节点们 */
    public itemCells: LoopListCell<T>[] = [];
    /**通用尺寸 */
    public size: cc.Size = null;
    /**通用锚点 */
    public anchor: cc.Vec2 = null;
    /**预制件 */
    public prefab: cc.Prefab = null;
    /**显示节点的池子 */
    public showNodePool: cc.Node[] = [];
    /**边缘数据 */
    public viewSides: { viewLeftSide?: number, viewRightSide?: number, viewBottomSide?: number, viewTopSide?: number, } = {};
    /**刷新显示的间隔次数 */
    public updateInv: number = 0;

    onLoad() {
        this.updateBorder()
    }

    /**
     * 更新边界
     */
    public updateBorder() {
        let view = this.scrollView.node.getChildByName("view");
        let wpos = getNodeWPos(view);
        //显示区域左边缘
        let viewLeftSide = wpos.x - (view.width * view.anchorX);
        //显示区域右边缘
        let viewRightSide = wpos.x + (view.width * (1 - view.anchorX));
        //显示区域下边缘
        let viewBottomSide = wpos.y - (view.height * view.anchorY);
        //显示区域上边缘
        let viewTopSide = wpos.y + (view.height * (1 - view.anchorY));
        this.viewSides = {
            viewLeftSide, viewRightSide, viewBottomSide, viewTopSide: viewTopSide
        };
    }

    /**
     * 初始化滚动列表
     * @param scrollView 
     */
    init(scrollView: cc.ScrollView, renderLayer?: cc.Node) {
        renderLayer && (this.renderLayer = renderLayer);
        this.scrollView = scrollView;
        this.updateBorder();
        this.scrollView.node.on("scrolling", this.scrollingListener, this);
    }

    /**滚动监听 */
    scrollingListener() {
        this.updateInv--;
        if (this.updateInv <= 0) {
            this.updateInv = 3;
            this.updateNodes();
        }
    }

    /**
     * 初始化
     * @param data 数据组
     * @param prefab 预制件
     * @param size 通用尺寸
     */
    public initData(data: T[], prefab: cc.Prefab, size?: cc.Size, anchor: cc.Vec2 = cc.v2(0.5, 0.5)) {
        this.datas = data;
        this.size = size;
        this.anchor = anchor;
        this.prefab = prefab;
        for (let i = 0, len = data.length; i < len; ++i) {
            let itemCell = this.itemCells[i];
            if (!itemCell) {
                let node = new cc.Node('LoopListCell')
                node.setParent(this.scrollView.content);
                node.setAnchorPoint(anchor);
                itemCell = this.itemCells[i] = node.addComponent('LoopListCell');
            }
            let data = this.datas[i];
            let cellSize = data.size ? data.size : size;
            itemCell.init(data, cellSize, this);
        }
        for (let i = data.length, len = this.itemCells.length; i < len; ++i) {
            this.recircleShowNode(this.itemCells[i].recircle());
            this.itemCells[i].destroy()
            this.itemCells[i] = null;
        }
        requestAnimationFrame(() => {
            this.updateNodes();
        })
    }

    /**
     * 压入新的数据
     * @param datas 
     */
    public pushDatas(datas: T[]) {
        let length = this.datas.length;
        for (let i = 0, len = datas.length; i < len; ++i) {
            let data = datas[i];
            this.datas.push(data);
            let index = length + i;
            this.addNodeFromIndex(index);
        }
    }
    /**
     * 插入新的数据
     * @param data 
     * @param insertIndex 
     */
    public insertData(data: T, insertIndex: number) {
        if (this.datas.length >= insertIndex) {
            this.datas.splice(insertIndex, 0, data);
            this.addNodeFromIndex(insertIndex);
        } else {
            this.pushDatas([data]);
        }
    }
    /**
     * 移除对应数据
     * @param removeIndex 
     */
    public removeData(removeIndex: number) {
        if (this.datas.length >= removeIndex) {
            this.datas.splice(removeIndex, 1);
            let itemCell = this.itemCells.splice(removeIndex, 1)[0];
            let nodeSides = getNodeBorder(itemCell.node, [true, true, true, true]);
            let changeSize = cc.size(-itemCell.size.width, -itemCell.size.height);
            itemCell.size = cc.size(0, 0);
            this.dealChangeSize(itemCell, nodeSides, changeSize).then(() => {
                this.recircleShowNode(itemCell.recircle())
                itemCell.node.destroy();
            });
        }
    }
    /**
     * 从某个数据开始更新
     * @param index 
     */
    private async addNodeFromIndex(index: number) {
        //获取当前的位置
        let nodeSides: IBorder = null;
        if (this.itemCells[index]) {
            nodeSides = getNodeBorder(this.itemCells[index].node, [true, true, true, true]);
        }
        let node = new cc.Node('LoopListCell')
        node.setParent(this.scrollView.content);
        node.setAnchorPoint(this.anchor);
        node.setSiblingIndex(index);
        let itemCell: LoopListCell<T> = node.addComponent('LoopListCell');
        this.itemCells.splice(index, 0, itemCell)
        let data = this.datas[index];
        let cellSize: cc.Size = data.size ? data.size : this.size;
        itemCell.init(data, cellSize, this);
        if (!nodeSides) {
            await new Promise<void>((resolve, reject) => {
                requestAnimationFrame(() => {
                    nodeSides = getNodeBorder(node, [true, true, true, true]);
                    resolve();
                })
            });
        }
        if (nodeSides) {
            itemCell.node.width = 0;
            itemCell.node.height = 0;
            this.dealChangeSize(itemCell, nodeSides, cellSize).then(() => {
                itemCell.node.width = cellSize.width;
                itemCell.node.height = cellSize.height;
            });
        }
        this.dealItemShow(itemCell);
    }

    /**更新节点的显示 */
    public updateNodes() {
        for (let i = 0, len = this.itemCells.length; i < len; ++i) {
            let itemCell = this.itemCells[i];
            this.dealItemShow(itemCell);
        }
    }

    /**
     * 处理cell的显示隐藏
     * @param itemCell 
     */
    private dealItemShow(itemCell: LoopListCell<T>) {
        if (itemCell) {
            let isShow = this.checkInView(itemCell);
            if (isShow) {
                itemCell.hasNoNode && itemCell.updateShow(isShow, this.prefab, this.showNodePool.length > 0 ? this.showNodePool.pop() : null, this.renderLayer);
            } else {
                !itemCell.hasNoNode && this.recircleShowNode(itemCell.recircle());
            }
        }
    }


    /**
     * 检查是否在屏幕内
     * @param node 
     * @returns 
     */
    private checkInView(itemCell: LoopListCell<T>) {
        let nodeSides = getNodeBorder(itemCell.node, [true, true, true, true]);
        //节点右侧边缘在屏幕左侧外面或者节点左边缘在屏幕右侧外面 则不在显示区域
        let horizontalOutside = nodeSides.rightSide < this.viewSides.viewLeftSide
            || nodeSides.leftSide > this.viewSides.viewRightSide;

        let verticalOutside = false;
        if (!horizontalOutside) {
            //节点上边缘在屏幕下方外面或者节点下边缘在屏幕上方外面 则不在显示区域
            verticalOutside = nodeSides.topSide < this.viewSides.viewBottomSide
                || nodeSides.bottomSide > this.viewSides.viewTopSide;
        }
        return !horizontalOutside && !verticalOutside;
    }
    /**
     * 处理变化的大小
     * @param cell 
     * @param nodeSides 
     * @param changeSize 
     */
    public async dealChangeSize(cell: LoopListCell<T>, nodeSides: IBorder, changeSize: cc.Size) {
        let topToBottomFulfill = (this.scrollView.content.anchorY == 1 && nodeSides.bottomSide >= this.viewSides.viewTopSide);
        let bottomToTopFulfill = (this.scrollView.content.anchorY == 0 && nodeSides.topSide <= this.viewSides.viewBottomSide);
        if (topToBottomFulfill || bottomToTopFulfill) {
            if (topToBottomFulfill) {
                this.scrollView.content.y += changeSize.height;
            } else {
                this.scrollView.content.y -= changeSize.height;
            }
        }
        let leftToRightFulfill = (this.scrollView.content.anchorX == 0 && nodeSides.rightSide <= this.viewSides.viewLeftSide);
        let RightToLeftFulfill = (this.scrollView.content.anchorX == 1 && nodeSides.leftSide >= this.viewSides.viewRightSide);
        if (leftToRightFulfill || RightToLeftFulfill) {
            if (RightToLeftFulfill) {
                this.scrollView.content.x += changeSize.width;
            } else {
                this.scrollView.content.x -= changeSize.width;
            }
        }
        if (!(topToBottomFulfill || bottomToTopFulfill || leftToRightFulfill || RightToLeftFulfill)) {
            cc.Tween.stopAllByTarget(cell.node);
            await new Promise<void>((resolve, reject) => {
                cc.tween(cell.node).to(0.3, { width: cell.size.width, height: cell.size.height }).call(() => {
                    resolve();
                }).start();
            })
        }
        this.updateNodes();
        return true;
    }

    /**
     * 回收显示节点
     * @param node 
     */
    private recircleShowNode(node: cc.Node) {
        node && this.showNodePool.push(node);
    }

    /**
     * 释放当前列表
     */
    public freeList() {
        this.scrollView.node.off("scrolling", this.scrollingListener);
        for (let i = 0, len = this.itemCells.length; i < len; ++i) {
            this.recircleShowNode(this.itemCells[i].recircle());
            this.itemCells[i].destroy();
        }
        this.itemCells = [];
        for (let index = 0, len = this.showNodePool.length; index < len; ++index) {
            this.showNodePool[index].destroy();
        }
        this.showNodePool = [];
        this.renderLayer = null;
        this.scrollView = null;
        this.prefab = null;
    }

}
/**
 * 获取节点的边缘
 * @param node 
 * @param needArr 顺序是上 下 左 右
 */
function getNodeBorder(node: cc.Node, needArr: boolean[]) {
    let sideArr: IBorder = {};
    let wpos = getNodeWPos(node);
    //显示区域上边缘
    if (needArr[0]) {
        sideArr.topSide = wpos.y + (node.height * (1 - node.anchorY));
    }
    //显示区域下边缘
    if (needArr[1]) {
        sideArr.bottomSide = wpos.y - (node.height * node.anchorY);
    }
    //显示区域左边缘
    if (needArr[2]) {
        sideArr.leftSide = wpos.x - (node.width * node.anchorX);
    }
    //显示区域右边缘
    if (needArr[3]) {
        sideArr.rightSide = wpos.x + (node.width * (1 - node.anchorX));
    }
    return sideArr;
}

/**
 * 获取节点世界坐标
 * @param node 
 * @returns 
 */
function getNodeWPos(node: cc.Node) {
    return node.parent.convertToWorldSpaceAR(node.position);
}


